//如果测试一直卡在MQTT连接上，请使用PubSubClient库的2.7版本（资源文件夹下有压缩包文件），目前测试最新版2.8无法连接
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <Ticker.h>
#include <ArduinoJson.h>

#define WIFI_SSID         "GT1"       //wifi名称
#define WIFI_PASSWORD     "8123456788"       //wifi密码

#define device_id         "66126f4c2ccc1a58388021bf_JIAJ"       //注册设备的ID和密钥
#define secret            "12345678"    
//MQTT三元组
#define ClientId          "66126f4c2ccc1a58388021bf_JIAJ_0_0_2024040709"
#define Username          "66126f4c2ccc1a58388021bf_JIAJ"
#define Password          "fe1a41a613defec50c4f88ca442b9a9b91d8da15ab26015cfb1a862acc365992"

#define MQTT_Address "d9a517dbcf.st1.iotda-device.cn-north-4.myhuaweicloud.com"
#define MQTT_Port 1883

#define Iot_link_Body_Format "{\"services\":[{\"service_id\":\"温湿度\",\"properties\":{%s" //Dev_data换成你产品里的服务ID
//{"services":[{"service_id":"Dev_data","properties":{"temp": 39}}]}
//设备属性上报
#define Iot_link_MQTT_Topic_Report "$oc/devices/"device_id"/sys/properties/report"
//接收平台下发的命令
#define Iot_link_MQTT_Topic_Commands "$oc/devices/"device_id"/sys/commands/#" 
//设备相应平台的命令
#define Iot_link_MQTT_Topic_CommandsRes "$oc/devices/"device_id"/sys/commands/response/request_id="
#define DHT11_IO_IN()  pinMode(4, INPUT)
#define DHT11_IO_OUT() pinMode(4, OUTPUT)
#define DHT11_DQ_OUT 4 //数据端口4
#define DHT11_DQ_IN  4  //数据端口4
#define u8 unsigned char
WiFiClient myesp8266Client;
PubSubClient myclient(myesp8266Client);
u8 temperature;
u8 humidity;
const int analogInPin = A0;
int sensorValue = 0;
long lastMsg = 0;

#define LEDPIN 0
//温湿度
void DHT11_Rst(void)
{
  DHT11_IO_OUT();                       //SET OUTPUT
  digitalWrite(DHT11_DQ_OUT, LOW);      //拉低DQ
  delay(20);                            //拉低至少18ms
  digitalWrite(DHT11_DQ_OUT, HIGH);     //DQ=1
  delayMicroseconds(30);                //主机拉高20~40us
}
//等待DHT11的回应
//返回1:未检测到DHT11的存在
//返回0:存在
u8 DHT11_Check(void)
{
  u8 retry = 0;
  DHT11_IO_IN();//SET INPUT
  while (digitalRead(DHT11_DQ_IN) && retry < 100) //DHT11会拉低40~80us
  {
    retry++;
    delayMicroseconds(1);
  };
  if (retry >= 100)return 1;
  else retry = 0;
  while (!digitalRead(DHT11_DQ_IN) && retry < 100) //DHT11拉低后会再次拉高40~80us
  {
    retry++;
    delayMicroseconds(1);
  };
  if (retry >= 100)return 1;
  return 0;
}
//从DHT11读取一个位
//返回值：1/0
u8 DHT11_Read_Bit(void)
{
  u8 retry = 0;
  while (digitalRead(DHT11_DQ_IN) && retry < 100) //等待变为低电平
  {
    retry++;
    delayMicroseconds(1);
  }
  retry = 0;
  while (!digitalRead(DHT11_DQ_IN) && retry < 100) //等待变高电平
  {
    retry++;
    delayMicroseconds(1);
  }
  delayMicroseconds(40);//等待40us
  if (digitalRead(DHT11_DQ_IN))return 1;
  else return 0;
}
//从DHT11读取一个字节
//返回值：读到的数据
u8 DHT11_Read_Byte(void)
{
  u8 i, dat;
  dat = 0;
  for (i = 0; i < 8; i++)
  {
    dat <<= 1;
    dat |= DHT11_Read_Bit();
  }
  return dat;
}
//从DHT11读取一次数据
//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值：0,正常;1,读取失败
u8 DHT11_Read_Data(u8 *temp, u8 *humi)
{
  u8 buf[5];
  u8 i;
  DHT11_Rst();
  if (DHT11_Check() == 0)
  {
    for (i = 0; i < 5; i++) //读取40位数据
    {
      buf[i] = DHT11_Read_Byte();
    }
    if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])
    {
      *humi = buf[0];
      *temp = buf[2];
    }
  } else return 1;
  return 0;
}
//初始化DHT11的IO口 DQ 同时检测DHT11的存在
//返回1:不存在
//返回0:存在
u8 DHT11_Init(void)
{
  pinMode(4, OUTPUT);
  DHT11_Rst();
  return DHT11_Check();
}
void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(LEDPIN,OUTPUT);
  digitalWrite(LEDPIN,LOW);  //LED低电平触发,高电平熄灭
  if (DHT11_Init()) //DHT11初始化
  {
    Serial.println("DHT11 Error");
    delay(200);
  }
  WIFI_Init();                //wifi初始化
  MQTT_Init();                //云平台连接初始化
}
unsigned long lastTick = 0;
void loop() {
  // put your main code here, to run repeatedly:
  if (!myclient.connected()){         //连接成功则不进行MQTT_Init();
    MQTT_Init();
  } 
  else myclient.loop();               //连接成则检查是否有数据上传，下发
  if (millis() - lastTick > 1000)
  {
    lastTick = millis();        //用于返回的Arduino板开始运行当前程序时的毫秒数。这个数字在大约50天后溢出，即回到零
    DHT11_Read_Data(&temperature, &humidity); //读取温湿度值
  }
  long now = millis();
  
  if (now - lastMsg > 5000) {
    lastMsg = now;
    sensorValue = analogRead(analogInPin);
    sensorValue=1024-sensorValue;
    MQTT_sjsq(temperature,humidity,sensorValue);
  }
}                                                
void MQTT_sjsq(u8 temp,u8 humi,int sensor)
{
  char properties[32];
  char properties1[32];
  char properties2[32];
  char jsonBuf[128];
  char jsonBuf1[128];
  char jsonBuf2[128];
 sprintf(properties,"\"温度\":%d}}]}",temp);                 //data_temp变量的值格式化为一个JSON字符串，并存储在properties字符串中
 sprintf(properties1,"\"湿度\":%d}}]}",humi);
  sprintf(properties2,"\"光照强度\":%d}}]}",sensor);
  sprintf(jsonBuf,Iot_link_Body_Format,properties);          //将properties字符串插入到另一个JSON模板Iot_link_Body_Format中。
  sprintf(jsonBuf1,Iot_link_Body_Format,properties1);
  sprintf(jsonBuf2,Iot_link_Body_Format,properties2);
 myclient.publish(Iot_link_MQTT_Topic_Report, jsonBuf);      //Iot_link_Body_Format中的占位符将被properties中的JSON字符串替换。jsonBuf存储最终生成的完整JSON消息。
 myclient.publish(Iot_link_MQTT_Topic_Report, jsonBuf1);
 myclient.publish(Iot_link_MQTT_Topic_Report, jsonBuf2);
  Serial.println(Iot_link_MQTT_Topic_Report);               //第一个参数Iot_link_MQTT_Topic_Report是MQTT主题（Topic），指定了消息将被发布到的频道。
  Serial.println(jsonBuf);
  Serial.println(jsonBuf1); 
  Serial.println(jsonBuf2);                                  //第二个参数jsonBuf是包含要发布的消息内容的字符串，这个消息遵循之前构建的JSON格式。
  Serial.println("MQTT Publish OK!");                       //publish方法将消息发送到MQTT代理服务器，然后服务器会将消息分发给订阅了该主题的所有客户端。
}
//连接wifi
void WIFI_Init()
{
    WiFi.mode(WIFI_STA);
    WiFi.begin(WIFI_SSID,WIFI_PASSWORD);
    while(WiFi.status()!=WL_CONNECTED)
    {
      delay(1000);
      Serial.println("WiFi Not Connect");
    }
    Serial.println("WiFi Connected OK!");
}

//连接MQTT
void MQTT_Init()
{
  myclient.setServer(MQTT_Address,MQTT_Port);                 
  while(!myclient.connected())
  {
    myclient.connect(ClientId,Username,Password);
  }
   myclient.setCallback(callback);//设定回调方式，当ESP8266收到订阅消息时会调用此方法
  boolean res = myclient.subscribe(Iot_link_MQTT_Topic_Commands); //连接成功时订阅主题commands
  if(res != true){
     Serial.printf("mqtt subscribe topic [%s] fail\n", Iot_link_MQTT_Topic_Commands);
  }
  Serial.printf("mqtt subscribe topic [%s] ok\n", Iot_link_MQTT_Topic_Commands);

  Serial.println("MQTT Connected OK!");

}
void callback(char* topic, byte* payload, unsigned int length)
{
  String recdata="";
  Serial.printf("them:");//订阅主题
  Serial.println(topic); 
  Serial.printf("data:");//订阅内容
  for(int i=0;i<length;i++)
  {
    recdata+=(char)payload[i];
  }
  Serial.println(recdata);
  //解析JSON数据
  DynamicJsonDocument jsonBuffer(1024);
  deserializeJson(jsonBuffer,recdata);            //将recdata字符串中的JSON数据解析到之前创建的jsonBuffer对象中。
                                                  //deserializeJson函数是DynamicJson库提供的一个函数，
                                                  //用于将JSON格式的字符串转换为DynamicJsonDocument可以处理的内部数据结构。
  JsonObject obj = jsonBuffer.as<JsonObject>(); //这行代码将jsonBuffer对象转换为JsonObject类型，并将其存储在名为obj的变量中
  
  String com = obj["paras"];                      //这行代码从obj对象中提取键为"paras"的值，并将其存储在名为com的String变量中
                                                  //[]操作符用于访问JSON对象中的键值对，这里它被用来获取名为"paras"的字段的值。
                                                  //如果该字段存在，com变量将包含其字符串值；如果不存在，com将包含一个空字符串。
  Serial.printf("jie_xi_mingling_com:");//解析命令
  Serial.println(com);
  
  deserializeJson(jsonBuffer,com);
  obj = jsonBuffer.as<JsonObject>();
  String ledcom = obj["test"];                  //这里的test换成你命令里的名字
  Serial.printf("jie_xi_led_mingling_ledcom:");//解析命令
  Serial.println(ledcom);
  
  //解析request id，设备响应时的topic需要包含命令的request id，且会动态变化
  char *p=topic;
  String request_id="";
  int i=0;
  while((*p++)!='\0')//获取topic长度
  {
    i++;
  }
  topic+=(i-36);    //移动指针位置
  
  for(int j=i-36;j<i;j++)//末36位为request id
    request_id+=*topic++;
  Serial.println("request_id："+request_id);
  Serial.println("mingling");//命令
  String param="{}";
  myclient.publish((Iot_link_MQTT_Topic_CommandsRes+request_id).c_str(),param.c_str());
  if(ledcom=="0")
  {
    digitalWrite(LEDPIN,HIGH);  //LED低电平触发,高电平熄灭
    Serial.println("off_led");  
  }
  else if(ledcom=="1")
  {
    digitalWrite(LEDPIN,LOW);  //LED低电平触发,高电平熄灭
    Serial.println("on_led");    
  }  
}

